package my.threading;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.locks.ReentrantLock;

public class StaticThread
{
	private static CountDownLatch latch = new CountDownLatch(3);
	private static CyclicBarrier barrier = new CyclicBarrier(2, new BarrierReached());
	private static ReentrantLock lock1 = new ReentrantLock();
	private static ReentrantLock lock2 = new ReentrantLock();
	
	public static synchronized void doThing() throws InterruptedException
	{
		// doing this is the same as putting synchronize on the method, static on a method synchronizes on the class object
		//  normally it is on the class instance
		//synchronized (StaticThread.class)
		//{
			System.out.println("Doing thing");
			Thread.sleep(2000);
		//}
		latch.countDown();
	}
	
	public static synchronized void doOtherThing() 
	{
		System.out.println("Doing other thing");
		latch.countDown();
	}
	
	public static void doOneMoreThing() 
	{
		System.out.println("Doing one more thing");
		latch.countDown();
	}
	
	public static void lockLock1()
	{
		lock1.lock();
		System.out.println("Thread: " + Thread.currentThread().getName() + " has the lock " + lock1.isHeldByCurrentThread());
	}
	
	public static void unlockLock1()
	{
		System.out.println("Thread: " + Thread.currentThread().getName() + " has the lock " + lock1.isHeldByCurrentThread() + " unlocking");
		lock1.unlock();
	}
	
	public static void lockLock2()
	{
		lock2.lock();
		System.out.println("Thread: " + Thread.currentThread().getName() + " has the lock " + lock2.isHeldByCurrentThread());
	}
	
	public static void unlockLock2()
	{
		System.out.println("Thread: " + Thread.currentThread().getName() + " has the lock " + lock2.isHeldByCurrentThread() + " unlocking");
		lock2.unlock();
	}
	
	public static void dropBar() throws InterruptedException, BrokenBarrierException
	{
		barrier.await();
	}
	
	public static void main(String [] args) throws InterruptedException
	{
		Thread t = new Thread(new Thing());
		Thread t2 = new Thread(new OtherThing());
		Thread t3 = new Thread(new OneMoreThing());
		
		Thread t4 = new Thread(new Lock1());
		Thread t5 = new Thread(new Lock2());
		
		// this thread will block the other thread because it implicitly blocks on StaticThread.class, when specified as synchronized
		t.start();
		t2.start();
		// this finishes 2nd as there is no blocking/syncrhonization on the function
		t3.start();
		
		// wait until the first 3 things get done
		StaticThread.latch.await();
		
		t4.start();
		t5.start();
		
		// the barrier trips when the specified number of awaits are called
		t = new Thread(new Barrier1());
		t2 = new Thread(new Barrier2(2000));
		t.start();
		t2.start();
		//Sleep so we don't prematurely bust the barrier
		Thread.sleep(3000);
		barrier.reset();
		
		// this should join faster
		t = new Thread(new Barrier1());
		t2 = new Thread(new Barrier2(500));
		t.start();
		t2.start();
		
	}	
}

class BarrierReached implements Runnable
{

	public void run()
	{
		System.out.println("Barrier was reached");
		
	}
	
}

class Barrier1 implements Runnable
{
	public void run()
	{
		try
		{
			System.out.println("Slapping Barrier");
			StaticThread.dropBar();
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BrokenBarrierException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class Barrier2 implements Runnable
{
	private int milli;
	public Barrier2(int milli)
	{
		this.milli = milli;
	}

	public void run()
	{
		try
		{
			System.out.println("Slapping Barrier2");
			Thread.sleep(milli);
			StaticThread.dropBar();
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BrokenBarrierException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class Lock1 implements Runnable
{
	public void run()
	{
		StaticThread.lockLock1();
		StaticThread.unlockLock1();	
	}
}

class Lock2 implements Runnable
{
	public void run()
	{
		// Calling this here depending on timing generally would throw an exception because it doesn't hold the lock
		// StaticThread.unlockLock1();
		StaticThread.lockLock2();
		StaticThread.unlockLock2();	
	}
	
}

class OneMoreThing implements Runnable
{
	public void run()
	{
		StaticThread.doOneMoreThing();		
	}
	
}

class OtherThing implements Runnable
{
	public void run()
	{
		StaticThread.doOtherThing();		
	}
	
}

class Thing implements Runnable
{
	public void run()
	{
		try
		{
			StaticThread.doThing();
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}
